package com.googlecode.contests_repo.validation;

import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.codehaus.jackson.map.ObjectMapper;

import com.googlecode.contests_repo.core.model.Problem;

/**
 * A validation helper class used to validate the structure of a problem.
 * 
 * @author joro, petko
 * 
 */
public class ProblemValidator extends ResourceValidator {

	private static final String TEST_PATTERN = "test\\.\\d\\d\\.(in|ans)";
	
	private static final Pattern pattern = Pattern.compile(TEST_PATTERN);

	/**
	 * Checks whether the given file contains valid data for a contest problem.
	 * Uses problem-schema.json for the validation.
	 * 
	 * @param folderPath
	 *            the path to the folder resource to validate
	 * @return a list of all validation errors
	 */
	public synchronized List<String> validate(String folderPath) {
		List<String> errors = super.validate(folderPath);
		if (errors.size() == 1 && errors.get(0).contains("does not exist")) {
			return errors;
		}
		File folder = new File(folderPath);
		
		//Ignore hidden files
		File[] allFiles = folder.listFiles(new FileFilter() {
			public boolean accept(File file) {
				return !file.isHidden();
			}
		});
		
		List<File> fileList = Arrays.asList(allFiles);

		// description*
		totalFilesCount += validateDescription(errors, folder, fileList);
		
		// checker
		totalFilesCount += validateChecker(errors, folder, fileList);
		

		// solution*
		totalFilesCount += ValidationTools.countFilesStartingWithPrefix(
				fileList, "solution", false);

		// test.in | test.ans
		totalFilesCount += validateTests(errors, folder, fileList);

		// extra files
		if (totalFilesCount != fileList.size()) {
			errors.add(String.format("Folder %s contains superfluous files.",
					folder.getAbsolutePath()));
		}

		return errors;
	}

	private int validateDescription(List<String> errors, File folder,
			List<File> fileList) {
		int descriptionCount = ValidationTools.countFilesStartingWithPrefix(
				fileList, "description", false);
		if (descriptionCount < 1) {
			errors.add(String.format(
					"Folder %s does not contain a problem description file.",
					folder.getAbsolutePath()));
		}

		return descriptionCount;
	}

	private int validateChecker(List<String> errors, File folder,
			List<File> fileList) {
		ObjectMapper mapper = new ObjectMapper();
		String checker = "";
		int checkersCount = 0;
		File metadataFile = new File(folder, "problem.json");
		try {
			Problem problem = mapper.readValue(metadataFile, Problem.class);
			checker = problem.getChecker();
		} catch (Exception e) {
			// do nothing
		}
		if (checker.equals("custom")) {
			checkersCount = ValidationTools.countFilesStartingWithPrefix(
					fileList, "checker", false);

			if (checkersCount != 1) {
				errors.add(String.format(
						"Folder %s should contain exactly one checker file.",
						folder.getAbsolutePath()));
			}
		}

		return checkersCount;
	}

	private int validateTests(List<String> errors, File folder,
			List<File> fileList) {
		int testFileCount = validateSingleTest(folder, fileList);
		if (testFileCount == 0) {
			testFileCount = validateMultipleTests(errors, folder, fileList);
		} else if (testFileCount == 1) {
			errors.add(String.format(
					"Folder %s has a missing test.in or test.ans file.",
					folder.getAbsolutePath()));
		}

		return testFileCount;
	}

	private int validateSingleTest(File folder, List<File> fileList) {
		int testFileCount = 0;
		String[] fileNames = new String[] { "test.in", "test.ans" };
		for (File file : fileList) {
			String fileName = file.getName();
			if (fileName.equals(fileNames[0]) || fileName.equals(fileNames[1])) {
				testFileCount++;
			}
		}

		return testFileCount;
	}

	private int validateMultipleTests(List<String> errors, File folder,
			List<File> fileList) {
		int testCount = 0;
		int maxTestNumber = 0;
		Set<String> set = new HashSet<String>();
		
		Matcher matcher;
		for (File file : fileList) {
			String fileName = file.getName();
			if (!file.isDirectory()) {
				matcher = pattern.matcher(fileName);
				if (matcher.matches()) {
					String filePrefix = fileName.substring(0,
							fileName.lastIndexOf('.'));
					int currentTestNumber = Integer.parseInt(fileName
							.substring(fileName.indexOf('.') + 1,
									fileName.lastIndexOf('.')));
					maxTestNumber = (maxTestNumber < currentTestNumber) ? currentTestNumber
							: maxTestNumber;
					if (!set.add(filePrefix)) {
						set.remove(filePrefix);
					}
					testCount++;
				}
			}
		}

		int testWeightCount = countTestWeights(new File(folder, "problem.json"));
		maxTestNumber++;
		if (!set.isEmpty()) {
			errors.add(String.format(
					"Folder %s: Some test.xx.in or test.xx.ans are missing.",
					folder.getAbsolutePath()));
		} else if (testCount == 0) {
			errors.add(String.format("Folder %s No test files found",
					folder.getAbsolutePath()));
		} else if (maxTestNumber != (testCount / 2)) {
			errors.add(String.format(
					"Folder %s: Some test.xx.in or test.xx.ans are missing.",
					folder.getAbsolutePath()));
		} else if (testWeightCount != 0 && testWeightCount != (testCount / 2)) {
			errors.add(String
					.format("Problem.json in folder %s has a mismatch between test files and test weights.",
							folder.getAbsolutePath()));
		}

		return testCount;
	}

	private int countTestWeights(File fileName) {
		int testWeightCount = 0;
		ObjectMapper mapper = new ObjectMapper();
		try {
			@SuppressWarnings("unchecked")
			Map<String, Object> map = mapper.readValue(fileName, Map.class);
			@SuppressWarnings("rawtypes")
			Collection weights = (Collection) map.get("testWeights");
			testWeightCount = weights == null ? 0 : weights.size();
		} catch (Exception e) {
			testWeightCount = -1;
		}

		return testWeightCount;
	}

	protected boolean jsonExists(List<String> errors, File folder,
			List<File> fileList) {
		File problemJasonFile = new File(folder, "problem.json");
		if (!fileList.contains(problemJasonFile)) {
			errors.add(String
					.format("Folder %s does not contain a \"problem.json\" file definition.",
							folder.getAbsolutePath()));
			return false;
		} else {
			List<String> validationErrors = ValidationTools.validateJson(
					problemJasonFile, Problem.class);
			errors.addAll(validationErrors);
			return true;
		}
	}
}
